/*
 * Flashlib - QB9 common Flash library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.qb9.flashlib.audio.player
{
	import com.qb9.flashlib.interfaces.IDisposable;
	import com.qb9.flashlib.net.LoadFile;
	import com.qb9.flashlib.net.LoadFileFormat;
	
	import flash.events.Event;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundTransform;
	
	public class AudioPlayer implements IDisposable
	{
		private var data:Object = {};
		private var master:Number = 1;
		private var base:String;
		private var crop:Number;
	
		public function AudioPlayer(base:String='', crop:Number=60)
		{
			this.base = base;
			this.crop = crop;
		}
	
		public function register(id:String, url:String = null, multiChannel:Boolean = false):LoadFile
		{
			return registerFile(new LoadFile(
				makeURL(url||id), LoadFileFormat.SOUND, id
			), multiChannel);
		}
		
		protected function makeURL(url:String):String
		{
			return base + url + '.mp3';
		}
		
		public function registerFile(file:LoadFile, multiChannel:Boolean = false, id:String = null):LoadFile
		{
			id = id || file.id;
			
			if (getData(id))
				disposeSound(id);
			
			data[id] = new AudioData(file, multiChannel);
				
			return file;
		}
		
	
		public function play(id:String):void
		{
			loop(id, 1);
		}
	
		public function loop(id:String, times:int = int.MAX_VALUE):void
		{
			if (isRunning(id))
			{
				if (getData(id).multiChannel)
					getChannel(id).removeEventListener(Event.SOUND_COMPLETE, soundComplete);
				else
					return;
			}
	
			// Work around flash's problems when looping
			getData(id).channel = getSound(id).play(crop, times, makeTransform(id));
			getChannel(id).addEventListener(Event.SOUND_COMPLETE, soundComplete);
		}
	
		public function stop(id:String=null):void
		{
			if (id === null)
			{
				for (id in data)
					stop(id);
				return;
			}
	
			if (!isRunning(id))
				return;
				
			getChannel(id).removeEventListener(Event.SOUND_COMPLETE, soundComplete);
			getChannel(id).stop();
			getData(id).channel = null;
		}
		
		private function getData(id:String):AudioData
		{
			return data[id] as AudioData;
		}
	
		public function has(id:String):Boolean
		{
			return getData(id) !== null;
		}
	
		private function soundComplete(e:Event):void
		{		
			for (var id:String in data)
				if (getChannel(id) === e.target)
					stop(id);
		}
	
		public function getFile(id:String):LoadFile
		{
			return getData(id).file;
		}
	
		public function getSound(id:String):Sound
		{
			return getData(id).sound;
		}
	
		// Further changes to masterVolume won't affect this task
		public function makeTask(id:String):AudioTask
		{
			var task:AudioTask = new AudioTask(getSound(id), crop);
			task.volume = getVolume(id);
			task.pan = getData(id).pan;
			return task;
		}
	
		public function gain(id:String, value:Number):void
		{
			getData(id).gain = value;
			updateRunningChannel(id);
		}
	
		public function pan(id:String, value:Number):void
		{
			getData(id).pan = value;
			updateRunningChannel(id);
		}
	
		private function updateRunningChannel(id:String):void
		{
			if (isRunning(id))
				getChannel(id).soundTransform = makeTransform(id);
		}
	
		private function getChannel(id:String):SoundChannel
		{
			return getData(id).channel;
		}
	
		public function isRunning(id:String):Boolean
		{
			return getChannel(id) !== null;
		}
	
		public function get masterVolume():Number
		{
			return master;
		}
	
		public function set masterVolume(v:Number):void
		{
			master = v;
	
			for (var id:String in data)
				updateRunningChannel(id);
		}
		
		private function getVolume(id:String):Number
		{
			return getData(id).gain * master;
		}
	
		private function makeTransform(id:String):SoundTransform
		{
			return new SoundTransform(getVolume(id), getData(id).pan);
		}
	
		public function disposeSound(id:String):void
		{
			stop(id);
			getData(id).dispose();
			delete data[id];
		}
		
		public function dispose():void
		{
			for (var id:String in data)
				disposeSound(id);
			data = {};
		}
	}
}
	import flash.media.SoundChannel;
	import com.qb9.flashlib.net.LoadFile;
	import flash.media.Sound;
	
	

internal class AudioData
{
	internal var multiChannel:Boolean;
	internal var file:LoadFile;
	internal var gain:Number = 1;
	internal var pan:Number = 0;
	internal var channel:SoundChannel;

	public function AudioData(file:LoadFile, multiChannel:Boolean)
	{
		this.file = file;
		this.multiChannel = multiChannel;
	}
	
	public function get sound() : Sound
	{
		return file.data as Sound;
	}
	
	public function dispose() : void
	{
		file.dispose();
		file = null;
		channel = null;
	}
}
